package com.mfn.sportjava.knowledge.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.mfn.sportjava.knowledge.model.KnowledgeEntry;
import com.mfn.sportjava.knowledge.mapper.KnowledgeEntryMapper;
import com.mfn.sportjava.knowledge.service.KnowledgeBaseService;
import com.mfn.sportjava.model.dto.MatchDTO;
import com.mfn.sportjava.util.MatchParsingUtil;
import io.milvus.client.MilvusServiceClient;
import io.milvus.grpc.SearchResults;
import io.milvus.param.MetricType;
import io.milvus.param.R;
import io.milvus.param.dml.SearchParam;
import io.milvus.response.SearchResultsWrapper;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.embedding.EmbeddingClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.lang.NonNull;
import org.springframework.util.CollectionUtils;


import java.util.*;
import java.util.stream.Collectors;

/**
 * @author Anyang
 * @version 1.0.0
 * @date 2025/05/25 12:05:24
 * @description 知识库服务实现
 */
@Service
@RequiredArgsConstructor
public class KnowledgeBaseServiceImpl implements KnowledgeBaseService {
    private static final Logger log = LoggerFactory.getLogger(KnowledgeBaseServiceImpl.class);

    private final KnowledgeEntryMapper knowledgeEntryMapper;
    private final Optional<MilvusServiceClient> milvusServiceClient;
    @NonNull
    private final EmbeddingClient embeddingClient;

    @Value("${spring.milvus.collection-name:match_knowledge}")
    private String collectionName;

    private static final String VECTOR_FIELD_NAME = "embedding";
    private static final String TEXT_CONTENT_FIELD_NAME = "text_content";
    private static final String MILVUS_PK_FIELD_NAME = "mysql_id"; // Milvus中存储MySQL ID的字段
    private static final int MILVUS_SEARCH_TOP_K_FACTOR = 2; // Milvus搜索时比实际需要多取的倍数
    private static final int DEFAULT_MILVUS_SEARCH_TOP_K = 10; // 默认的Milvus搜索TopK

    /**
     * @param match 比赛信息DTO
     * @param maxResults 结果数量上限
     * @return {@link List }<{@link String }>
     * 搜索会持续进行，直到找到 {@code maxResults} 数量的唯一知识片段，
     */
    @Override
    @Transactional(readOnly = true)
    public List<String> searchRelevantKnowledge(MatchDTO match, int maxResults) {
        List<String> results = new ArrayList<>();
        Set<String> uniqueContents = new HashSet<>(); // 用于全局去重
        log.debug("知识库服务: 开始为比赛 {} 搜索知识库, 最大结果数: {}", match, maxResults);

        // 1. MySQL 精确匹配
        if (results.size() < maxResults) {
            searchWithMySqlExactMatch(match, maxResults - results.size(), uniqueContents, results);
        }

        // 2. MySQL 更广泛匹配
        if (results.size() < maxResults) {
            searchWithMySqlBroadMatch(match, maxResults - results.size(), uniqueContents, results);
        }

        // 3. MySQL 通用球队信息
        if (results.size() < maxResults) {
            searchWithMySqlGeneralTeamInfo(match, maxResults - results.size(), uniqueContents, results);
        }

        // 4. Milvus 向量搜索
        if (results.size() < maxResults) {
            searchWithMilvus(match, maxResults - results.size(), uniqueContents, results);
        }

        log.info("知识库服务: 为比赛 {} 找到 {} 条相关知识片段 (需求: {} 条)。", match, results.size(), maxResults);
        return results.stream().limit(maxResults).collect(Collectors.toList());
    }

    /**
     * 通过 MatchKey 进行 MySQL 精确匹配搜索。
     *
     * @param match            比赛信息DTO
     * @param maxResultsNeeded 当前还需要的结果数量上限
     * @param uniqueContents   一个用于存储已找到的唯一知识内容的集合，用于去重。
     * @param currentResults   当前已累积的结果列表，新找到的内容将添加到此列表。
     */
    private void searchWithMySqlExactMatch(MatchDTO match, int maxResultsNeeded, Set<String> uniqueContents, List<String> currentResults) {
        if (maxResultsNeeded <= 0) return;

        String matchKey = KnowledgeEntry.generateMatchKey(match);
        log.debug("MySQL精确匹配: 生成的 MatchKey: {}", matchKey);
        KnowledgeEntry exactEntry = knowledgeEntryMapper.selectOne(
                new QueryWrapper<KnowledgeEntry>().eq("match_key", matchKey)
        );
        if (exactEntry != null) {
            log.info("MySQL精确匹配: 成功 (MatchKey: {})", matchKey);
            if (uniqueContents.add(exactEntry.getContent())) {
                currentResults.add(exactEntry.getContent());
                log.debug("MySQL精确匹配: 添加内容到结果列表。");
            }
        }
    }

    /**
     * 通过联赛、主队和客队进行 MySQL 广泛匹配搜索。
     *
     * @param match            比赛信息DTO
     * @param maxResultsNeeded 当前还需要的结果数量上限
     * @param uniqueContents   一个用于存储已找到的唯一知识内容的集合，用于去重。
     * @param currentResults   当前已累积的结果列表，新找到的内容将添加到此列表。
     */
    private void searchWithMySqlBroadMatch(MatchDTO match, int maxResultsNeeded, Set<String> uniqueContents, List<String> currentResults) {
        if (maxResultsNeeded <= 0) return;

        String[] teams = MatchParsingUtil.parseTeamsFromMatchString(match.getMatch());
        if (teams != null) {
            String homeTeam = teams[0];
            String awayTeam = teams[1];
            log.debug("MySQL广泛匹配: 联赛: {}, 主队: {}, 客队: {}", match.getLeague(), homeTeam, awayTeam);
            List<KnowledgeEntry> teamEntries = knowledgeEntryMapper
                    .findByLeagueAndHomeTeamAndAwayTeamOrderByCreatedAtDesc(match.getLeague(), homeTeam, awayTeam);

            teamEntries.stream()
                    .map(KnowledgeEntry::getContent)
                    .filter(uniqueContents::add)
                    .limit(maxResultsNeeded - currentResults.size()) // 注意：这里limit的是新添加的数量
                    .forEach(content -> {
                        log.debug("MySQL广泛匹配: 找到并添加内容。");
                        currentResults.add(content);
                    });
        }
    }

    /**
     * 通过联赛和单个球队（作为主队或客队）进行 MySQL 通用球队信息搜索。
     *
     * @param match            比赛信息DTO
     * @param maxResultsNeeded 当前还需要的结果数量上限
     * @param uniqueContents   一个用于存储已找到的唯一知识内容的集合，用于去重。
     * @param currentResults   当前已累积的结果列表，新找到的内容将添加到此列表。
     */
    private void searchWithMySqlGeneralTeamInfo(MatchDTO match, int maxResultsNeeded, Set<String> uniqueContents, List<String> currentResults) {
        if (maxResultsNeeded <= 0) return;

        String[] teams = MatchParsingUtil.parseTeamsFromMatchString(match.getMatch());
        if (teams != null) {
            log.debug("MySQL通用球队信息: 球队: {} 和 {}", teams[0], teams[1]);
            Set<KnowledgeEntry> generalTeamEntriesSet = new LinkedHashSet<>();

            // 查询包含主队的比赛
            if (currentResults.size() < maxResultsNeeded + currentResults.size()) {
                List<KnowledgeEntry> homeTeamEntries = knowledgeEntryMapper.findByLeagueAndTeamOrderByCreatedAtDesc(match.getLeague(), teams[0]);
                if(!CollectionUtils.isEmpty(homeTeamEntries)) generalTeamEntriesSet.addAll(homeTeamEntries);
            }
            // 查询包含客队的比赛
            if (currentResults.size() < maxResultsNeeded + currentResults.size() && !teams[0].equalsIgnoreCase(teams[1])) {
                List<KnowledgeEntry> awayTeamEntries = knowledgeEntryMapper.findByLeagueAndTeamOrderByCreatedAtDesc(match.getLeague(), teams[1]);
                if(!CollectionUtils.isEmpty(awayTeamEntries)) generalTeamEntriesSet.addAll(awayTeamEntries);
            }

            long newAdditions = generalTeamEntriesSet.stream()
                    .map(KnowledgeEntry::getContent)
                    .filter(uniqueContents::add)
                    .limit(maxResultsNeeded - currentResults.size())
                    .peek(content -> log.debug("MySQL通用球队信息: 找到并添加内容。"))
                    .toList()
                    .size();
            log.debug("MySQL通用球队信息: 新增 {} 条记录", newAdditions);
        }
    }

    /**
     * 使用 Milvus 进行向量搜索。
     *
     * @param match            比赛信息DTO
     * @param maxResultsNeeded 当前还需要的结果数量上限
     * @param uniqueContents   一个用于存储已找到的唯一知识内容的集合，用于去重。
     * @param currentResults   当前已累积的结果列表，新找到的内容将添加到此列表。
     */
    private void searchWithMilvus(MatchDTO match, int maxResultsNeeded, Set<String> uniqueContents, List<String> currentResults) {
        if (maxResultsNeeded <= 0 || milvusServiceClient.isEmpty()) {
            if (milvusServiceClient.isEmpty() && maxResultsNeeded > 0) {
                log.warn("知识库服务: MilvusServiceClient 不可用，跳过向量搜索。");
            }
            return;
        }
        log.debug("知识库服务: MySQL 结果数 ({}) < 需求数 ({}). 尝试 Milvus 向量搜索。", currentResults.size(), maxResultsNeeded + currentResults.size());

        try {
            int milvusSearchLimit = Math.max(maxResultsNeeded * MILVUS_SEARCH_TOP_K_FACTOR, DEFAULT_MILVUS_SEARCH_TOP_K);
            List<String> milvusRawResults = performMilvusSearchInternal(match, milvusSearchLimit);

            milvusRawResults.stream()
                    .filter(uniqueContents::add)
                    .limit(maxResultsNeeded)
                    .forEach(content -> {
                        log.debug("Milvus向量搜索: 添加内容到结果列表。");
                        currentResults.add(content);
                    });
        } catch (Exception e) {
            log.error("知识库服务: Milvus 搜索时发生错误 (比赛: {}): {}", match, e.getMessage(), e);
        }
    }

    /**
     * 执行实际的 Milvus 搜索操作。
     *
     * @param match 比赛信息DTO
     * @param topK  Milvus 搜索的 topK 参数
     * @return 从 Milvus 返回的原始文本内容列表，可能包含重复项。
     */
    private List<String> performMilvusSearchInternal(MatchDTO match, int topK) {
        if (topK <= 0 || milvusServiceClient.isEmpty()) {
            return Collections.emptyList();
        }

        MilvusServiceClient client = milvusServiceClient.get();
        String queryText = String.format("比赛分析查询: 联赛=%s, 比赛=%s, 日期=%s %s",
                match.getLeague(), match.getMatch(), match.getDate(), match.getTime());
        log.debug("Milvus内部搜索: 正在为查询生成嵌入向量: '{}'", queryText);

        List<Float> queryVector;
        try {
            List<Double> embeddingDoubleList = embeddingClient.embed(queryText);
            if (CollectionUtils.isEmpty(embeddingDoubleList)) {
                log.error("Milvus内部搜索: 无法为查询文本生成嵌入向量 (返回为空或null)。");
                return Collections.emptyList();
            }
            queryVector = embeddingDoubleList.stream().map(Double::floatValue).collect(Collectors.toList());
        } catch (Exception e) {
            log.error("Milvus内部搜索: 生成嵌入向量时出错: {}", e.getMessage(), e);
            return Collections.emptyList();
        }

        List<String> outputFields = List.of(TEXT_CONTENT_FIELD_NAME, MILVUS_PK_FIELD_NAME);

        SearchParam searchParam = SearchParam.newBuilder()
                .withCollectionName(collectionName)
                .withMetricType(MetricType.L2)
                .withTopK(topK)
                .withVectorFieldName(VECTOR_FIELD_NAME)
                .withOutFields(outputFields)
                .withFloatVectors(Collections.singletonList(queryVector))
                .build();

        log.debug("Milvus内部搜索: 执行搜索, Collection: '{}', TopK: {}, OutputFields: {}", collectionName, topK, outputFields);
        R<SearchResults> response;
        try {
            response = client.search(searchParam);
        } catch (Exception e) {
            log.error("Milvus内部搜索: 执行搜索时发生异常: {}", e.getMessage(), e);
            return Collections.emptyList();
        }

        if (response.getStatus() != R.Status.Success.getCode()) {
            log.error("Milvus内部搜索: 搜索失败: Status={}, Message={}", response.getStatus(), response.getMessage());
            if (response.getException() != null) log.error("Milvus内部搜索: 搜索异常详情:", response.getException());
            return Collections.emptyList();
        }

        List<String> foundContent = new ArrayList<>();
        try {
            SearchResultsWrapper wrapper = new SearchResultsWrapper(response.getData().getResults());
            if (wrapper.getRowRecords(0) == null || wrapper.getRowRecords(0).isEmpty()) {
                log.debug("Milvus内部搜索: 搜索没有返回任何结果。");
                return Collections.emptyList();
            }
            List<SearchResultsWrapper.IDScore> scores = wrapper.getIDScore(0);
            List<?> textDataList = wrapper.getFieldData(TEXT_CONTENT_FIELD_NAME, 0);

            if (CollectionUtils.isEmpty(textDataList)) {
                log.error("Milvus内部搜索: 搜索响应未包含请求的输出字段 '{}'。", TEXT_CONTENT_FIELD_NAME);
                return Collections.emptyList();
            }

            getSearchResult(scores, textDataList, foundContent);
        } catch (Exception e) {
            log.error("Milvus内部搜索: 处理搜索结果时出错: {}", e.getMessage(), e);
            return Collections.emptyList();
        }

        log.info("Milvus内部搜索: 成功返回 {} 个结果 (请求TopK: {}).", foundContent.size(), topK);
        return foundContent;
    }

    /**
     * 从搜索结果中提取内容。
     *
     * @param scores           搜索结果中的分数列表
     * @param textDataList     搜索结果中的文本数据列表
     * @param foundContent     存储提取到的内容列表
     */
    private static void getSearchResult(List<SearchResultsWrapper.IDScore> scores, List<?> textDataList, List<String> foundContent) {
        int resultSize = Math.min(scores.size(), textDataList.size());
        for (int i = 0; i < resultSize; ++i) {
            Object contentObj = textDataList.get(i);
            if (contentObj instanceof String) {
                String content = (String) contentObj;
                log.trace("Milvus内部搜索结果: Score={}, 内容片段: {}...", scores.get(i).getScore(), content.substring(0, Math.min(content.length(), 50)));
                foundContent.add(content);
            } else {
                log.warn("Milvus内部搜索: 结果中索引 {} 处的数据类型非预期。期望 String，实际为: {}", i, (contentObj == null ? "null" : contentObj.getClass().getName()));
            }
        }
    }
}
